package com.pocket.insight.base.mql.relation;

import com.pocket.insight.base.exception.BizException;
import com.pocket.insight.base.mql.Conversion;
import com.pocket.insight.base.mql.Relation;
import com.pocket.insight.base.mql.TypedVal;
import com.pocket.insight.base.sql.BaseSchemaProvider;
import com.pocket.insight.base.sql.Dialect;
import com.pocket.insight.base.sql.SchemaProvider;
import com.pocket.insight.base.sql.context.WithToTempContext;
import com.pocket.insight.base.sql.dialect.VirtualDialect;
import com.pocket.insight.entity.Dataset;
import com.pocket.insight.entity.ME;
import com.pocket.insight.entity.Type;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;

/**
 * 描述：
 *
 * @author shuize
 * @create 2024/12/5
 */
public abstract class BaseRelation implements Relation {

    public    String          alias;
    public    Dialect         dialect;
    protected Long            relationSize;
    public    Boolean         isSourceVirtualDialect;
    private   RelationRunTime runTime = new RelationRunTime();
    private   Conversion      conversion;
    public    List<Relation>  with    = new ArrayList<>();
    public Boolean withRecursive;

    @Override
    public BaseRelation setAlias(String alias) {
        this.alias = alias;
        return this;
    }

    @Override
    public TypedVal getQueryForReused(TypedVal query, String alias, Boolean noNeedToReuse) {
        if (this.fetchRunTime().withUids == null) {
            this.fetchRunTime().withUids = new ArrayList();
        }

        Pair<TypedVal, String> queryAliasPair = this.getConversion().getQueryForReused(query, alias, this.getAlias(), this.fetchRunTime().withUids, noNeedToReuse, this.fetchRunTime().accessCount);
        if (queryAliasPair != null && WithToTempContext.isDoWithReuse()) {
            query = queryAliasPair.getLeft();
            if (queryAliasPair.getRight() != null) {
                alias = queryAliasPair.getRight();
                Relation with;
                if (this instanceof PrimeRelation) {
                    with = PrimeRelation.create(this.getDialect(), ME.create(ME.Kind.dataset, query)).setAlias(alias);
                    with.fetchRunTime().accessCount = this.fetchRunTime().accessCount;
                    with.fetchRunTime().clearWhenReuse = true;
                    this.getWith().add(with);
                } else {
                    with = PrimeRelation.create(this.getDialect(), ME.create(ME.Kind.dataset, query)).setAlias(alias);
                    with.fetchRunTime().accessCount = this.fetchRunTime().accessCount;
                    with.fetchRunTime().clearWhenReuse = true;
                    this.getWith().add(0, with);
                }

                query = new TypedVal(this.getDialect().quoteTableName(alias), Type.TypeName.dataset);
                this.fetchRunTime().withUids.add(alias);
                this.fetchRunTime().query = query;
                return query;
            } else {
                return query;
            }
        } else {
            return query;
        }
    }


    public Conversion getConversion() {
        if (this.conversion == null) {
            this.conversion = Conversion.createConversion(this.dialect);
        }

        return this.conversion;
    }

    protected TypedVal getFromQuery(Relation from, Boolean addStar, Boolean usedByStatement, Boolean noNeedToReuse) {
        if (from == null) {
            return null;
        }
        TypedVal query = this.toQueryWithSchema(from, addStar, usedByStatement);
        if (query == null) {
            query = from.toQuery();
        }
        return this.getConversion().bracketQueryWithAlias(from.getQueryForReused(query, null, noNeedToReuse), from.getAlias());
    }

    private TypedVal toQueryWithSchema(Relation from, Boolean addStar, Boolean useByStatement) {
        if (from.fetchRunTime().query != null) {
            return from.fetchRunTime().query;
        }
        if (from instanceof StatementRelation statementRelation && CollectionUtils.isEmpty(statementRelation.select)) {
            return null;
        }

        SchemaProvider schemaProvider = from.getSchemaProvider();
        if (!(schemaProvider instanceof BaseSchemaProvider)) {
            return null;
        }
        Set<Pair<String, Object>> useFields = new LinkedHashSet(from.fetchRunTime().fieldsTouched);
        if (useFields.isEmpty()) {
            return null;
        }
        StatementRelation relation = Relation.statement(from.getDialect());
        if (this instanceof StatementRelation statementRelation) {
            relation.ignoreCastError = statementRelation.ignoreCastError;
        }

        boolean toRewrite = false;
        List<Dataset.Field> fields = new ArrayList();
        boolean toAddStar = addStar;
        Set<String> usedFieldNames = new HashSet();
        Map<String, ME> fieldOriginExprMap = new HashMap();
        Iterator fieldIterator = useFields.iterator();
//
//        while(true) {
//            Dataset.Field field;
//            do {
//                ME me;
//                if (!fieldIterator.hasNext()) {
//                    if (BooleanUtils.isNotFalse(from.getSplitFieldsRequired())) {
//                        schemaProvider.getFields().forEach((fieldx) -> {
//                            if (fieldx != null && fieldx.getFieldName() != null && !usedFieldNames.contains(fieldx.getFieldName()) && fieldx.isUserAdded()) {
//                                if (fieldx.isUserAdded() && (EvalAdapter.getUsedFunctions(fieldx.getExpr()).stream().anyMatch((e) -> {
//                                    return e.equalsIgnoreCase(HDF.LIST_ELEMENTS.toString()) || e.equalsIgnoreCase(HDF.JSONARRAY_ELEMENTS.toString());
//                                }) || PrepareExprRewriter.isExprSplitJsonWithArray(fieldx.getExpr()))) {
//                                    fieldOriginExprMap.put(fieldx.getFieldName(), fieldx.getExpr());
//                                    fieldx.setExpr(rewriteFields(fieldx.getExpr(), (DatasetSchemaProvider)schemaProvider, from.getAlias()).setUid(fieldx.getFieldName()).setType(fieldx.getType()));
//                                    fields.add(fieldx);
//                                    usedFieldNames.add(fieldx.getFieldName());
//                                }
//
//                            }
//                        });
//                    }
//
//                    var12 = fields.iterator();
//
//                    while(var12.hasNext()) {
//                        Dataset.Field field1 = (Dataset.Field)var12.next();
//                        if (!field1.getIsMetric()) {
//                            if (field1.isUserAdded()) {
//                                he = field1.getExpr();
//                                if (!toRewrite && needRewrite(relation, he)) {
//                                    toRewrite = true;
//                                }
//
//                                relation.project(he);
//                            } else if (!toAddStar) {
//                                relation.project(HE.create(Kind.field, field1.getFieldName(), (List)null, field1.getFieldName()).setDataset(field1.getDatasetId()));
//                            }
//                        }
//                    }
//
//                    if (toRewrite) {
//                        from.fetchRunTime().fieldsTouched.clear();
//                        relation.scan(from);
//                        if (toAddStar) {
//                            relation.select.add(0, HE.create(Kind.constant, Star.STAR));
//                        }
//
//                        Relation result = Rewriter.applyRulesForPrime(relation);
//                        TypedVal tableQuery = result instanceof StatementRelation ? ((StatementRelation)result).toSqlImpl(new AtomicBoolean(false)) : result.toQuery();
//                        List<Dataset.Field> backupFields = new ArrayList();
//                        Iterator var25 = fields.iterator();
//
//                        while(true) {
//                            Dataset.Field field;
//                            do {
//                                if (!var25.hasNext()) {
//                                    if (!useByStatement) {
//                                        tableQuery = this.getConversion().bracketQueryWithAlias(tableQuery, from.getAlias());
//                                    }
//
//                                    from.fetchRunTime().query = tableQuery;
//                                    from.fetchRunTime().backupSchema = backupFields;
//                                    return tableQuery;
//                                }
//
//                                field = (Dataset.Field)var25.next();
//                            } while(toAddStar && !field.isUserAdded());
//
//                            Dataset.Field backupField = field.partialDeepCopy();
//                            if (fieldOriginExprMap.containsKey(field.getFieldName())) {
//                                backupField.getOptions().setExpr((HE)fieldOriginExprMap.get(field.getFieldName()));
//                            }
//
//                            backupFields.add(backupField);
//                            field.setExpr((HE)null);
//                            field.getGeneratedOptions().setOriginType(field.fetchType());
//                            field.getGeneratedOptions().setNativeType((String)null);
//                            field.getGeneratedOptions().setSqlType((Integer)null);
//                            field.setDbFieldName((String)null);
//                        }
//                    }
//
//                    if (useByStatement && from instanceof PrimeRelation) {
//                        PrimeRelation primeRelation = (PrimeRelation)from;
//                        HE he = primeRelation.dataset;
//                        if (he != null && he.kind == Kind.dataset && he.op instanceof String) {
//                            String customSql = (String)he.op;
//                            if (customSql.startsWith("(SELECT ") && customSql.endsWith(")")) {
//                                from.fetchRunTime().query = new TypedVal(customSql.substring(1, customSql.length() - 1));
//                                return from.fetchRunTime().query;
//                            }
//
//                            return null;
//                        }
//                    }
//
//                    return null;
//                }
//
//                Pair<String, Object> useField = (Pair)var12.next();
//                he = HE.create(Kind.field, useField.getLeft(), (List)null, (String)useField.getLeft());
//                field = schemaProvider.getFieldByFieldNameOrLabel(he);
//            } while(field != null && field.getFieldName() != null && usedFieldNames.contains(field.getFieldName()));
//
//            if (field != null) {
//                if (field.isUserAdded()) {
//                    fieldOriginExprMap.put(field.getFieldName(), field.getExpr());
//                    field.setExpr(rewriteFields(field.getExpr(), (DatasetSchemaProvider)schemaProvider, from.getAlias()).setUid(field.getFieldName()).setType(field.getType()));
//                    if (toAddStar && EvalAdapter.getUsedFunctions(field.getExpr()).stream().anyMatch((e) -> {
//                        return e.equals(HDF.TOP_N.toString()) || e.equals(HDF.BOTTOM_N.toString());
//                    })) {
//                        toAddStar = false;
//                    }
//                }
//
//                fields.add(field);
//                usedFieldNames.add(field.getFieldName());
//            }
//        }
        return null;
    }

    @Override
    public Boolean getIsSourceVirtualDialect(Dialect dialect) {
        Boolean result = BooleanUtils.isTrue(this.isSourceVirtualDialect) || this.dialect == null;
        if (dialect != null && result && !dialect.supportPushToMemory()) {
            throw new BizException("ERROR_COMMON_DICTIONARY_UNSUPPORTED: " + dialect.getDialectName());
        } else {
            return result;
        }
    }

    @Override
    public String getAlias() {
        return this.alias;
    }

    @Override
    public Long getRelationSize() {
        return relationSize;
    }

    @Override
    public Relation setRelationSize(Long size) {
        this.relationSize = size;
        return this;
    }

    @Override
    public RelationRunTime fetchRunTime() {
        if (this.runTime == null) {
            this.runTime = new RelationRunTime();
        }
        return this.runTime;
    }

    @Override
    public TypedVal toQuery() {
        return null;
    }

    @Override
    public Dialect getDialect() {
        return this.dialect;
    }

    @Override
    public void setDialect(Dialect dialect) {
        if (this.dialect instanceof VirtualDialect && this.isSourceVirtualDialect == null) {
            this.isSourceVirtualDialect = true;
        }
        this.dialect = dialect;
    }

    @Override
    public SchemaProvider getSchemaProvider() {
        return BaseSchemaProvider.create();
    }

    @Override
    public void clearRunTime() {
        putRunTime(null);
        getWith().removeIf(w -> BooleanUtils.isTrue((w.fetchRunTime()).clearWhenReuse));
        getWith().forEach(w -> w.clearRunTime());
    }

    @Override
    public List<Relation> getWith() {
        return this.with;
    }

    @Override
    public void setWithRecursive(Boolean withRecursive) {
        this.withRecursive = withRecursive;
    }

    public void putRunTime(RelationRunTime relationRunTime) {
        this.runTime = relationRunTime;
    }


    public static class RelationRunTime {
        public    TypedVal                  query;
        public    Boolean                   isRewritten             = Boolean.valueOf(false);
        public    Relation                  resultRewritten;
        protected Set<Pair<String, Object>> fieldsTouched           = new LinkedHashSet<>();
        protected List<ME>                  relatedExprs            = new ArrayList<>();
        public    Integer                   accessCount             = Integer.valueOf(0);
        protected Map<String, TypedVal>     constantSelectUidValMap = new HashMap<>();
        protected List<Dataset.Field>       backupSchema;
        public    Boolean                   clearWhenReuse;
        public    List<List<ME>>            relatedWheres           = new ArrayList<>();
        public    Boolean                   relatedWheresSame;
        public    Boolean                   clearSelectWhenReuse;
        public    List<String>              withUids;
    }
}
